package com.lwlk.util.flow;

import com.lwlk.util.functional.Common;
import com.lwlk.util.functional.Method2;
import com.lwlk.util.functional.TypeTransformer;
import com.lwlk.util.tuple.Tuple2;
import com.lwlk.util.tuple.Tuple3;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static com.lwlk.util.functional.Common.returnThat;
import static com.lwlk.util.rubbish.havefun.WTF.P;

// 必须统一出口的Switch 不然返回值全是Object怎么知道到底返回的是什么
public class Switch<ResultType> {

    private final List<Tuple2<SwitchIn, SwitchOut<ResultType>>> items;

    private Switch() { this.items = new ArrayList<>(); }

    private static <ResultType> Switch<ResultType> newInstance() { return new Switch<>(); }

    public <ElementType> Switch<ResultType> when(SwitchIn<ElementType> in, SwitchOut<ResultType> out) {
        return returnThat(() -> items.add(Tuple2.with(in, out)), this);
    }

    public Optional<SwitchOut<ResultType>> match(Object object) {
        return items.stream().filter((Tuple2<SwitchIn, SwitchOut<ResultType>> tuple) -> tuple.getValue1().accept(object)).findFirst().flatMap((e) -> Optional.of(e.getValue2()));
    }

    private static class SwitchIn<ElementType> {

        private final ElementType element;

        private final List<Tuple3<Class, TypeTransformer, Method2>> typeTransformers;

        public SwitchIn(ElementType element) {
            this.element = element;
            this.typeTransformers = new ArrayList<>();
        }

        // 同类型下的相等语义
        public SwitchIn<ElementType> justEqual(Class clazz) {
            return returnThat(() -> typeTransformers.add(Tuple3.with(clazz, Common::identity, Objects::equals)), this);
        }

        // 转型后的相等语义
        public <OuterType> SwitchIn<ElementType> transformedEqual(Class clazz, TypeTransformer<ElementType, OuterType> transformer) {
            return returnThat(() -> typeTransformers.add(Tuple3.with(clazz, transformer, Objects::equals)), this);
        }

        // 转型后进行判定
        public <OuterType> SwitchIn<ElementType> dealWith(Class clazz, TypeTransformer<ElementType, OuterType> transformer, Method2<Boolean, OuterType, OuterType> acceptor) {
            return returnThat(() -> typeTransformers.add(Tuple3.with(clazz, transformer, acceptor)), this);
        }

        // 不进行转型 直接进行判定
        public <OuterType> SwitchIn<ElementType> ignoreTransform(Class clazz, Method2<Boolean, OuterType, ElementType> acceptor) {
            return returnThat(() -> typeTransformers.add(Tuple3.with(clazz, Common::identity, acceptor)), this);
        }

        // 该项是否接受入参的主逻辑
        public boolean accept(Object object) { // 如果入参类型和池中的某类型同名... 那么转型并应用判定器 返回是否接受该参数
            for (Tuple3<Class, TypeTransformer, Method2> tuple : typeTransformers) {
                if (tuple.getValue1().getName().equals(object.getClass().getName())) {
                    Try.ChoiceResult choice = Try.choice(
                            // 注意顺序 先是外源参数 然后是内部属性
                            () -> tuple.getValue3().call(object, tuple.getValue2().call(element)),
                            () -> null);
                    // 转型和判定过程中的一切异常均否定该入参
                    if (choice.isSuccess() && (boolean) choice.getResult()) {
                        return true;
                    }
                }
            }
            return false;
        }
    }

    private static class SwitchOut<ResultType> {
        private final ResultType result;
        public SwitchOut(ResultType result) { this.result = result; }
        public ResultType getResult() { return result; }
    }

    public static void main(String[] args) {

        Switch<String> sw = Switch.newInstance();

        SwitchIn<Integer> in1 = new SwitchIn<>(100)
                .transformedEqual(String.class, (i) -> "" + i)
                .transformedEqual(Integer.class, (i) -> i + 1)
                .transformedEqual(Integer.class, (i) -> i - 1);

        SwitchIn<String> in2 = new SwitchIn<>("X200")
                .ignoreTransform(String.class, (out, in) -> in.equals("X" + out));

        sw = sw.when(in1, new SwitchOut<>("accept item 100")).when(in2, new SwitchOut<>("accept item 200"));

        P(sw.match("100"));
        P(sw.match("200"));
        P(sw.match("X200"));
        P(sw.match(100));
        P(sw.match(99));
        P(sw.match(101));
    }
}
